package org.cove.ape {
	import flash.errors.IllegalOperationError;	

	/**
	 * @author cbra
	 */
	public class Vector3f {
		public static const ZERO:Vector3f = new Vector3f(0, 0, 0);
		public static const UNIT_X:Vector3f = new Vector3f(1, 0, 0);
		public static const UNIT_Y:Vector3f = new Vector3f(0, 1, 0);
		public static const UNIT_Z:Vector3f = new Vector3f(0, 0, 1);
		public static const UNIT_XYZ:Vector3f = new Vector3f(1, 1, 1);

		/**
		 * the x value of the vector.
		 */
		public var x:Number;

		/**
		 * the y value of the vector.
		 */
		public var y:Number;

		/**
		 * the z value of the vector.
		 */
		public var z:Number;

		/**
		 * Constructor instantiates a new <code>Vector3f</code> with provides
		 * values.
		 *
		 * @param x
		 *            the x value of the vector.
		 * @param y
		 *            the y value of the vector.
		 * @param z
		 *            the z value of the vector.
		 */
		public function Vector3f(x:Number = 0, y:Number = 0, z:Number = 0) {
			this.x = x;
			this.y = y;
			this.z = z;
		}
		
		public function clone():Vector3f{
			return new Vector3f(x,y,z);
		}

		/**
		 *
		 * <code>add</code> adds a provided vector to this vector creating a
		 * resultant vector which is returned. If the provided vector is null, null
		 * is returned.
		 *
		 * @param vec
		 *            the vector to add to this.
		 * @return the resultant vector.
		 */
		public function add(vec:Vector3f ):Vector3f {
			if (null == vec) {
				return null;
			}
			return new Vector3f(x + vec.x, y + vec.y, z + vec.z);
		}

		/**
		 * <code>set</code> sets the x,y,z values of the vector based on passed
		 * parameters.
		 *
		 * @param x
		 *            the x value of the vector.
		 * @param y
		 *            the y value of the vector.
		 * @param z
		 *            the z value of the vector.
		 * @return this vector
		 */
		public function set(x:Number,y:Number,z:Number):Vector3f {
			this.x = x;
			this.y = y;
			this.z = z;
			return this;
		}

		/**
		 * <code>addLocal</code> adds a provided vector to this vector internally,
		 * and returns a handle to this vector for easy chaining of calls. If the
		 * provided vector is null, null is returned.
		 *
		 * @param vec
		 *            the vector to add to this vector.
		 * @return this
		 */
		public function addLocal(vec:Vector3f ):Vector3f {
			if (null == vec) {
				return null;
			}
			x += vec.x;
			y += vec.y;
			z += vec.z;
			return this;
		}

		/**
		 *
		 * <code>dot</code> calculates the dot product of this vector with a
		 * provided vector. If the provided vector is null, 0 is returned.
		 *
		 * @param vec
		 *            the vector to dot with this vector.
		 * @return the resultant dot product of this vector and a given vector.
		 */
		public function dot(vec:Vector3f ):Number {
			if (null == vec) {
				return 0;
			}
			return x * vec.x + y * vec.y + z * vec.z;
		}

		
		/**
		 * <code>cross</code> calculates the cross product of this vector with a
		 * parameter vector v.  The result is stored in <code>result</code>
		 *
		 * @param otherX
		 *            x component of the vector to take the cross product of with this.
		 * @param otherY
		 *            y component of the vector to take the cross product of with this.
		 * @param otherZ
		 *            z component of the vector to take the cross product of with this.
		 * @param result
		 *            the vector to store the cross product result.
		 * @return result, after recieving the cross product vector.
		 */
		public function cross(otherX:Number , otherY:Number , otherZ:Number, result:Vector3f):Vector3f {
			if (result == null) result = new Vector3f();
			var resX:Number = ((y * otherZ) - (z * otherY)); 
			var resY:Number = ((z * otherX) - (x * otherZ));
			var resZ:Number = ((x * otherY) - (y * otherX));
			result.set(resX, resY, resZ);
			return result;
		}

		/**
		 * <code>cross</code> calculates the cross product of this vector with a
		 * parameter vector v.
		 *
		 * @param v
		 *            the vector to take the cross product of with this.
		 * @return the cross product vector.
		 */
		public function crossVector( v:Vector3f):Vector3f {
			return crossVectorBuffered(v, null);
		}

		/**
		 * <code>cross</code> calculates the cross product of this vector with a
		 * parameter vector v.  The result is stored in <code>result</code>
		 *
		 * @param v
		 *            the vector to take the cross product of with this.
		 * @param result
		 *            the vector to store the cross product result.
		 * @return result, after recieving the cross product vector.
		 */
		public function crossVectorBuffered(v:Vector3f ,result:Vector3f ):Vector3f {
			return cross(v.x, v.y, v.z, result);
		}

		/**
		 * <code>length</code> calculates the magnitude of this vector.
		 *
		 * @return the length or magnitude of the vector.
		 */
		public function length():Number {
			return Math.sqrt(lengthSquared());
		}

		/**
		 * <code>lengthSquared</code> calculates the squared value of the
		 * magnitude of the vector.
		 *
		 * @return the magnitude squared of the vector.
		 */
		public function lengthSquared():Number {
			return x * x + y * y + z * z;
		}

		/**
		 * <code>distanceSquared</code> calculates the distance squared between
		 * this vector and vector v.
		 *
		 * @param v the second vector to determine the distance squared.
		 * @return the distance squared between the two vectors.
		 */
		public function distanceSquared(v:Vector3f):Number {
			var dx:Number = x - v.x;
			var dy:Number = y - v.y;
			var dz:Number = z - v.z;
			return  (dx * dx + dy * dy + dz * dz);
		}

		/**
		 * <code>distance</code> calculates the distance between this vector and
		 * vector v.
		 *
		 * @param v the second vector to determine the distance.
		 * @return the distance between the two vectors.
		 */
		public function distance(v:Vector3f ):Number {
			return Math.sqrt(distanceSquared(v));
		}

		/**
		 *
		 * <code>mult</code> multiplies this vector by a scalar. The resultant
		 * vector is returned.
		 *
		 * @param scalar
		 *            the value to multiply this vector by.
		 * @return the new vector.
		 */
		public function multScalar( scalar:Number):Vector3f {
			return new Vector3f(x * scalar, y * scalar, z * scalar);
		}

		/**
		 * <code>multLocal</code> multiplies a provided vector to this vector
		 * internally, and returns a handle to this vector for easy chaining of
		 * calls. If the provided vector is null, null is returned.
		 *
		 * @param vec
		 *            the vector to mult to this vector.
		 * @return this
		 */
		public function multVector(vec:Vector3f ):Vector3f {
			if (null == vec) {
				return null;
			}
     
			return new Vector3f(x * vec.x, y * vec.y, z * vec.z);
		}

		/**
		 * <code>divide</code> divides the values of this vector by a scalar and
		 * returns the result. The values of this vector remain untouched.
		 *
		 * @param scalar
		 *            the value to divide this vectors attributes by.
		 * @return the result <code>Vector</code>.
		 */
		public function divideScalar( scalar:Number):Vector3f {
			scalar = 1.0 / scalar;
			return new Vector3f(x * scalar, y * scalar, z * scalar);
		}

		/**
		 * <code>divide</code> divides the values of this vector by a scalar and
		 * returns the result. The values of this vector remain untouched.
		 *
		 * @param scalar
		 *            the value to divide this vectors attributes by.
		 * @return the result <code>Vector</code>.
		 */
		public function divideVector(scalar:Vector3f ):Vector3f {
			return new Vector3f(x / scalar.x, y / scalar.y, z / scalar.z);
		}

		
		/**
		 *
		 * <code>negate</code> returns the negative of this vector. All values are
		 * negated and set to a new vector.
		 *
		 * @return the negated vector.
		 */
		public function negate():Vector3f {
			return new Vector3f(-x, -y, -z);
		}

		/**
		 *
		 * <code>negateLocal</code> negates the internal values of this vector.
		 *
		 * @return this.
		 */
		public function negateLocal():Vector3f {
			x = -x;
			y = -y;
			z = -z;
			return this;
		}

		/**
		 *
		 * <code>subtract</code> subtracts the values of a given vector from those
		 * of this vector creating a new vector object. If the provided vector is
		 * null, null is returned.
		 *
		 * @param vec
		 *            the vector to subtract from this vector.
		 * @return the result vector.
		 */
		public function subtract(vec:Vector3f ):Vector3f {
			return new Vector3f(x - vec.x, y - vec.y, z - vec.z);
		}

		/**
		 * <code>divideLocalVector</code> divides this vector by a scalar internally,
		 * and returns a handle to this vector for easy chaining of calls. Dividing
		 * by zero will result in an exception.
		 *
		 * @param scalar
		 *            the value to divides this vector by.
		 * @return this
		 */
		public function divideLocalVector(scalar:Vector3f ):Vector3f {
			x /= scalar.x;
			y /= scalar.y;
			z /= scalar.z;
			return this;
		}

		/**
		 * <code>divideLocalScalar</code> divides this vector by a scalar internally,
		 * and returns a handle to this vector for easy chaining of calls. Dividing
		 * by zero will result in an exception.
		 *
		 * @param scalar
		 *            the value to divides this vector by.
		 * @return this
		 */
		public function divideLocalScalar(scalar:Number):Vector3f {
			scalar = 1.0 / scalar;
			x *= scalar;
			y *= scalar;
			z *= scalar;
			return this;
		}

		/**
		 * <code>normalizeLocal</code> makes this vector into a unit vector of
		 * itself.
		 *
		 * @return this.
		 */
		public function normalizeLocal():Vector3f {
			var length:Number = length();
			if (length != 0) {
				return divideLocalScalar(length);
			}
			return this;        
		}

		/**
		 * <code>normalize</code> returns the unit vector of this vector.
		 *
		 * @return unit vector of this vector.
		 */
		public function normalize():Vector3f {
			var length:Number = length();
			if (length != 0) {
				return divideScalar(length);
			}
        
			return divideScalar(1);      
		}

		/**
		 * <code>angleBetween</code> returns (in radians) the angle between two vectors.
		 * It is assumed that both this vector and the given vector are unit vectors (iow, normalized).
		 * 
		 * @param otherVector a unit vector to find the angle against
		 * @return the angle in radians.
		 */
		public function angleBetween(otherVector:Vector3f ):Number {
			var dotProduct:Number = dot(otherVector);
			var angle:Number = Math.acos(dotProduct);
			return angle;
		}

		/**
		 * <code>zero</code> resets this vector's data to zero internally.
		 */
		public function zero():void {
			x = y = z = 0;
		}

		/**
		 * @param index
		 * @return x value if index == 0, y value if index == 1 or z value if index ==
		 *         2
		 * @throws IllegalArgumentException
		 *             if index is not one of 0, 1, 2.
		 */
		public function getByIndex(index:int):Number {
			switch (index) {
				case 0:
					return x;
				case 1:
					return y;
				case 2:
					return z;
			}
			throw new IllegalOperationError("index must be either 0, 1 or 2");
		}

		/**
		 * @param index
		 *            which field index in this vector to set.
		 * @param value
		 *            to set to one of x, y or z.
		 * @throws IllegalArgumentException
		 *             if index is not one of 0, 1, 2.
		 */
		public function setByIndex( index:int, value:Number):void {
			switch (index) {
				case 0:
					x = value;
					return;
				case 1:
					y = value;
					return;
				case 2:
					z = value;
					return;
			}
			throw new IllegalOperationError("index must be either 0, 1 or 2");
		}
		
		public function toString() {
			return "("+x+","+y+"," +z +")";
		}
	}
}
